package cn.xsshome.algorithmNation.util;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * rsa加解密、密钥解析工具类
 *
 * @author 小天
 * @version 1.0.0, 2017/10/18 0018 09:57
 */
public enum RsaUtils {

    /**
     * 单例
     */
    INSTANCE;

    public static final String MD5_WITH_RSA = "MD5withRSA";
    public static final String SHA1_WITH_RSA = "SHA1WithRSA";
    public static final String SHA256_WITH_RSA = "SHA256WithRSA";
    public static final String SHA512_WITH_RSA = "SHA512withRSA";
    public static final String RSA_ECB_PKCS1PADDING = "RSA/ECB/PKCS1Padding";
    public KeyFactory keyFactory;
    private Logger logger = LoggerFactory.getLogger(getClass());

    RsaUtils() {
        try {
            keyFactory = KeyFactory.getInstance("RSA");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }

    /**
     * 从base64字符串中加载 rsa PKCS#8格式 私钥
     *
     * @param priKeyBase64Str base64编码的rsa私钥字符串
     * @return {@link RSAPrivateKey}
     * @throws InvalidKeySpecException
     */
    public RSAPrivateKey loadPKCS8PriKeyFromBase64(String priKeyBase64Str) throws InvalidKeySpecException {
        byte[] priKeyBytes = Base64.decode(priKeyBase64Str);
        return loadPKCS8PriKey(priKeyBytes);
    }

    /**
     * 从byte数组加载rsa PKCS#8格式 私钥
     *
     * @param priKeyBytes 私钥字节数组
     * @return {@link RSAPrivateKey}
     * @throws InvalidKeySpecException
     */
    public RSAPrivateKey loadPKCS8PriKey(byte[] priKeyBytes) throws InvalidKeySpecException {
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(priKeyBytes);
        return (RSAPrivateKey) keyFactory.generatePrivate(keySpec);
    }

    /**
     * 从base64字符串中加载rsa公钥
     *
     * @param pubKeyBase64Str base64编码的rsa公钥字符串
     * @return {@link RSAPublicKey}
     * @throws InvalidKeySpecException
     */
    public RSAPublicKey loadX509PubKeyFromBase64(String pubKeyBase64Str) throws InvalidKeySpecException {
        byte[] pubKeyBytes = Base64.decode(pubKeyBase64Str);
        return loadX509PubKey(pubKeyBytes);
    }

    /**
     * 从byte数组加载rsa公钥
     *
     * @param pubKeyBytes 公钥字节数组
     * @return {@link RSAPublicKey}
     * @throws InvalidKeySpecException
     */
    public RSAPublicKey loadX509PubKey(byte[] pubKeyBytes) throws InvalidKeySpecException {
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(pubKeyBytes);
        return (RSAPublicKey) keyFactory.generatePublic(keySpec);
    }

    /**
     * rsa解密
     *
     * @param rsaPrivateKey   rsa私钥
     * @param cipherTxtBase64 base64密文
     * @return UTF-8 编码的明文
     */
    public String decryptBase64String(RSAPrivateKey rsaPrivateKey, String cipherTxtBase64)
            throws IllegalBlockSizeException, BadPaddingException, NoSuchPaddingException, InvalidKeyException {
        return decryptBase64String(rsaPrivateKey, cipherTxtBase64, Charset.defaultCharset(), Charset.defaultCharset());
    }

    /**
     * rsa解密
     *
     * @param rsaPrivateKey    rsa私钥
     * @param cipherTxtBase64  base64密文
     * @param cipherTxtCharset 密文编码
     * @param plainTxtCharset  解密后的明文编码
     * @return plainTxtCharset 编码的明文
     */
    public String decryptBase64String(RSAPrivateKey rsaPrivateKey, String cipherTxtBase64, Charset cipherTxtCharset,
                                      Charset plainTxtCharset)
            throws IllegalBlockSizeException, BadPaddingException, NoSuchPaddingException, InvalidKeyException {
        byte[] res = decrypt(rsaPrivateKey, Base64.decode(cipherTxtBase64.getBytes()));
        if (res == null) {
            return null;
        }
        return StrUtil.str(res, plainTxtCharset);
    }

    /**
     * rsa私钥解密
     *
     * @param rsaPrivateKey rsa私钥
     * @param cipherTxt     明文
     * @return 解密结果
     */
    public byte[] decrypt(RSAPrivateKey rsaPrivateKey, byte[] cipherTxt)
            throws InvalidKeyException, NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ECB_PKCS1PADDING);
            cipher.init(Cipher.DECRYPT_MODE, rsaPrivateKey);
            return cipher.doFinal(cipherTxt);
        } catch (NoSuchAlgorithmException e) {
            // 一般rsa算法都存在，忽略该异常
            logger.error("", e);
        }
        return null;
    }

    /**
     * rsa加密
     *
     * @param rsaPublicKey rsa 公钥
     * @param plainText    明文
     * @return 加密结果
     * @throws InvalidKeyException
     * @throws NoSuchPaddingException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     */
    public byte[] encrypt(RSAPublicKey rsaPublicKey, String plainText)
            throws InvalidKeyException, NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException {
        byte[] res = encrypt(rsaPublicKey, plainText.getBytes());
        if (res == null) {
            return null;
        }
        return res;
    }

    /**
     * rsa加密
     *
     * @param rsaPublicKey rsa 公钥
     * @param plainText    明文字节数组
     * @return cipherTxtCharset 编码的base64密文
     * @throws InvalidKeyException
     * @throws NoSuchPaddingException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     */
    public String encryptToBase64(RSAPublicKey rsaPublicKey, byte[] plainText)
            throws InvalidKeyException, NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException {
        return encryptToBase64(rsaPublicKey, plainText, Charset.defaultCharset());
    }

    /**
     * rsa加密
     *
     * @param rsaPublicKey rsa 公钥
     * @param plainText    明文
     * @return cipherTxtCharset 编码的base64密文
     * @throws InvalidKeyException
     * @throws NoSuchPaddingException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     */
    public String encryptToBase64(RSAPublicKey rsaPublicKey, String plainText)
            throws InvalidKeyException, NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException {
        return encryptToBase64(rsaPublicKey, plainText, Charset.defaultCharset(), Charset.defaultCharset());
    }

    /**
     * rsa加密
     *
     * @param rsaPublicKey     rsa 公钥
     * @param plainText        明文
     * @param plainTxtCharset  明文编码
     * @param cipherTxtCharset 加密结果转字符串的编码
     * @return cipherTxtCharset 编码的base64密文
     * @throws InvalidKeyException
     * @throws NoSuchPaddingException
     * @throws BadPaddingException
     * @throws IllegalBlockSizeException
     */
    public String encryptToBase64(RSAPublicKey rsaPublicKey, String plainText, Charset plainTxtCharset,
                                  Charset cipherTxtCharset)
            throws InvalidKeyException, NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException {
        return encryptToBase64(rsaPublicKey, plainText.getBytes(), cipherTxtCharset);
    }

    /**
     * rsa公钥加密
     *
     * @param rsaPublicKey     rsa公钥
     * @param plainText        明文
     * @param cipherTxtCharset 加密结果转字符串的编码
     * @return 加密结果
     */
    public String encryptToBase64(RSAPublicKey rsaPublicKey, byte[] plainText, Charset cipherTxtCharset)
            throws IllegalBlockSizeException, BadPaddingException, NoSuchPaddingException, InvalidKeyException {
        byte[] res = encrypt(rsaPublicKey, plainText);
        if (res == null) {
            return null;
        }
        return StrUtil.str(Base64.encode(res), cipherTxtCharset);
    }

    /**
     * rsa公钥加密
     *
     * @param rsaPublicKey rsa公钥
     * @param plainText    明文字节数组
     * @return 加密结果
     */
    public byte[] encrypt(RSAPublicKey rsaPublicKey, byte[] plainText)
            throws InvalidKeyException, NoSuchPaddingException, BadPaddingException, IllegalBlockSizeException {
        try {
            Cipher cipher = Cipher.getInstance(RSA_ECB_PKCS1PADDING);
            cipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey);
            return cipher.doFinal(plainText);
        } catch (NoSuchAlgorithmException e) {
            // 一般rsa算法都存在，忽略该异常
            logger.error("", e);
        }
        return null;
    }
}